package com.m1.tzolkin.model.board;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;

import com.m1.tzolkin.model.IAction;
import com.m1.tzolkin.model.IStartingWealth;
import com.m1.tzolkin.model.ITZStock;
import com.m1.tzolkin.model.Season;
import com.m1.tzolkin.model.StartingWealth;
import com.m1.tzolkin.model.TZResource;
import com.m1.tzolkin.model.TZStock;
import com.m1.tzolkin.model.buildables.IActionBuilding;
import com.m1.tzolkin.model.buildables.IFarm;
import com.m1.tzolkin.model.buildables.IMonument;
import com.m1.tzolkin.model.exceptions.CannotBuildException;
import com.m1.tzolkin.model.exceptions.EndOfGameException;
import com.m1.tzolkin.model.exceptions.IllegalWorkerOperationException;
import com.m1.tzolkin.model.exceptions.NoAvailableSkullException;
import com.m1.tzolkin.model.exceptions.NoMovedWorkersException;
import com.m1.tzolkin.model.exceptions.NoWorkerHereException;
import com.m1.tzolkin.model.exceptions.NotAvailableActionException;
import com.m1.tzolkin.model.exceptions.NotEnoughCornException;
import com.m1.tzolkin.model.exceptions.NotEnoughResourceException;
import com.m1.tzolkin.model.exceptions.StepMoveException;
import com.m1.tzolkin.model.exceptions.WaitingOperationsException;
import com.m1.tzolkin.model.players.ITZPlayer;
import com.m1.tzolkin.model.technos.ITechnologiesManager;
import com.m1.tzolkin.model.technos.ITechnology;
import com.m1.tzolkin.model.technos.ITechnology.TechLevel;
import com.m1.tzolkin.model.technos.Technologies;
import com.m1.tzolkin.model.technos.Technologies.Architecture;
import com.m1.tzolkin.model.technos.Technologies.Theology;
import com.m1.tzolkin.model.technos.TechnologiesManager;
import com.m1.tzolkin.model.temples.ITemple;
import com.m1.tzolkin.model.wheels.IWheel;
import com.m1.tzolkin.model.wheels.IWheelPosition;
import com.m1.tzolkin.model.wheels.WheelEnum;

public class TZBoard extends TechBoard implements ITZBoard {
	
	// ATTRIBUTS

	private static final long serialVersionUID = 4694376503227149746L;
	
	private final Map<IAction, Integer> actions;
	// Gestionnaire de technologies
	private final ITechnologiesManager technologyManager;
	// Doit construire en payant du ma�s
	private boolean mustPayInCorn;
	// Somme que le joueur doit payer
	private final ITZStock toPay;
	// Quantit� que le joueur doit payer
	private int randomToPay;
	// Nombre d'unit�s quelconques que le joueur peut �conomiser
	private int canSave;
	// Nombre de b�timents construits � la suite
	private int built;
	// Bâtiment d'action éventuellement en construction
	private IActionBuilding building;
	// Monument �ventuellement en construction
	private IMonument monument;
	// Ferme �ventuellement en construction
	private IFarm farm;
	// Technologie en cours d'up
	private ITechnology technology;
	
	// CONSTRUCTEUR
	
	public TZBoard(Collection<ITZPlayer> players) {
		super(players);
		toPay = new TZStock();
		actions = new HashMap<IAction, Integer>();
		// Initialisation des technologies
		technologyManager = new TechnologiesManager(this);
		for (ITZPlayer p : players) {
			technologyManager.addKey(p);
		}
		technologyManager.addPropertyChangeListener(new PropertyChangeListener() {
			@Override
			public void propertyChange(PropertyChangeEvent event) {
				listeners.firePropertyChange("technologiesManager", null, null);
			}
		});
		// Activation des tuiles de richesse de d�part
		for (ITZPlayer p : getPlayers()) {
			for (IStartingWealth c : p.getCards()) {
				try {
					c.active(this, p);
				} catch (Exception e) { /* Rien */ }
			}
		}
		// Blocage �ventuel des cases selon les tuiles restantes
		int ef = getPlayers().size();
		if (ef < MAX_PLAYERS) {
			IStartingWealth[] tiles = StartingWealth.values();
			// Tri des tuiles d�j� attribu�es
			List<IStartingWealth> val = new ArrayList<IStartingWealth>();
			ITZPlayer owner;
			for (IStartingWealth sw : tiles) {
				owner = null;
				for (ITZPlayer p : getPlayers()) {
					if (p.getCards().contains(sw)) {
						owner = p;
						break;
					}
				}
				if (owner == null) {
					val.add(sw);
				}
			}
			// Blocage en cons�quence
			Collection<IWheel> placed = new HashSet<IWheel>();
			IWheelPosition wp;
			IWheel w;
			int i = 0;
			int total = (MAX_PLAYERS - ef) * MAX_WORKERS;
			while (!val.isEmpty() && i < total) {
				int position = (int) (Math.random() * val.size());
				wp = val.get(position).getValue();
				w = getWheel(wp.getWheel());
				w.block(wp.getPosition());
				if (!placed.contains(w) && !wp.getWheel().equals(WheelEnum.CHICHEN_ITZA)) {
					w.block((wp.getPosition() + w.getFullLength() / 2) % w.getFullLength());
					placed.add(w);
					++i;
				}
				val.remove(position);
				++i;
			}
		}
	}
	
	// REQUETES
	
	@Override
	public Collection<TechLevel> getTechnologies(ITZPlayer p) {
		return technologyManager.getTechs(p);
	}
	
	@Override
	public int getTechLevel(ITechnology t, ITZPlayer p) {
		return technologyManager.getPosition(t, p);
	}

	@Override
	public Map<IAction, Integer> getActions() {
		return new HashMap<IAction, Integer>(actions);
	}
	
	@Override
	public ITZStock mustPay() {
		return toPay;
	}
	
	@Override
	public int canSave() {
		return canSave;
	}

	@Override
	public boolean mustPayInCorn() {
		return mustPayInCorn;
	}
	
	@Override
	public int randomToPay() {
		return randomToPay;
	}
	
	@Override
	public boolean canUpTemple(ITemple t, ITZPlayer p) {
		return getTemplesManager().canUp(t, p);
	}
	
	@Override
	public boolean canDownTemple(ITemple t, ITZPlayer p) {
		return getTemplesManager().canDown(t, p);
	}
	
	// COMMANDES
	
	@Override
	public void upTech(ITechnology t, ITZPlayer p) {
		if (t == null || p == null) {
			throw new IllegalArgumentException();
		}
		if (canUpTechs() <= 0 || randomToPay > 0) {
			throw new IllegalStateException();
		}
		int oldLevel = technologyManager.getPosition(t, p);
		technologyManager.up(t, p);
		listeners.firePropertyChange("techLevels", oldLevel, technologyManager.getPosition(t, p));
		setTechEvolve(canUpTechs() - 1, mustPayTech());
		getLog().addSentence(new String[] {
				p.getName(), "EVOLVE", "1", "TECH_LEVEL", "IN", t.toString()
		});
	}
	
	@Override
	public void incTurn() throws EndOfGameException, WaitingOperationsException {
		incTurn(1);
	}
	
	@Override
	public void incTurn(int d) throws EndOfGameException, WaitingOperationsException {
		if (isWaitingForTurn() == 0 || (d > MINIMUM_DAY_TURN && getFirstWorker() != null && !getFirstWorker().getOwner().canTurnTwice())) {
			throw new IllegalStateException();
		}
		// Jour de festin
		Season feedingDay = getTurnsCounter().feedingDay(); 
		if (feedingDay != null) {
			getTemplesManager().activeTemples(this, feedingDay);
		}
		super.incTurn(d);
		// Fin de partie ?
		if (getTurn() > DAYS) {
			// Monuments
			for (ITZPlayer p : getPlayers()) {
				for (IMonument m : p.getMonuments()) {
					try {
						m.active(this, p);
					} catch (Exception e) { /* Rien */ }
				}
			}
			listeners.firePropertyChange("endOfGame", null, null);
		}
		listeners.firePropertyChange("turn", getTurn() - d, getTurn());
	}
	
	@Override
	public ITZPlayer endTurn() throws WaitingOperationsException, EndOfGameException, NoMovedWorkersException {
		actions.clear();
		listeners.firePropertyChange("actions", null, actions.keySet());
		return super.endTurn();
	}
	
	@Override
	public Map<IAction, Integer> takeWorker(WheelEnum w, int position) throws IllegalWorkerOperationException, NoWorkerHereException {
		setActions(super.takeWorker(w, position));
		IWheel wheel = getWheel(w);
		if (w.equals(WheelEnum.CHICHEN_ITZA) && getTechnologies(getCurrentPlayer()).contains(Theology.NEXT_CIACTION)
				&& position < wheel.getCircuitLength() - wheel.getBonusLength()) {
			actions.put(wheel.getActions()[position + 1], 0);
		}
		built = 0;
		listeners.firePropertyChange("actions", null, actions.keySet());
		listeners.firePropertyChange("begforcorn", getCurrentPlayer().getCorn() < LIMIT_CORN_FOR_ASK, false);
		return actions;
	}

	@Override
	public void setActions(Map<IAction, Integer> act) {
		if (isWaitingForTurn() != 0) {
			throw new IllegalStateException();
		}
		if (act == null) {
			throw new IllegalArgumentException();
		}
		actions.clear();
		for (IAction a : act.keySet()) {
			if (act.get(a) <= getPlayersManager().getCurrent().getCorn()) {
				actions.put(a, act.get(a));
			}
		}
		listeners.firePropertyChange("actions", null, actions.keySet());
	}
	
	@Override
	public void doAction(IAction a) throws WaitingOperationsException, NoAvailableSkullException, NotEnoughCornException, NotAvailableActionException {
		if (a == null) {
			throw new IllegalArgumentException();
		}
		if (isWaitingForTurn() != 0) {
			throw new WaitingOperationsException();
		}
		if (!actions.containsKey(a) || !a.isAvailable()) {
			throw new NotAvailableActionException();
		}
		getCurrentPlayer().removeCorn(actions.get(a));
		actions.clear();
		listeners.firePropertyChange("actions", null, actions.keySet());
		a.doAction(this, getCurrentPlayer());
	}
	
	@Override
	public int wannaUpTech(ITechnology t) throws NotEnoughResourceException, NotAvailableActionException {
		if (canUpTechs() < 1) {
			throw new NotAvailableActionException();
		}
		int toPay = Math.max(1, (technologyManager.getPosition(t, getCurrentPlayer()) + 1) % 4);
		if (!mustPayTech() || toPay == 0) {
			upTech(t, getCurrentPlayer());
		} else {
			if (getCurrentPlayer().getWallet().getTotal() - getCurrentPlayer().getWallet().getSkulls() < toPay) {
				throw new NotEnoughResourceException();
			}
			technology = t;
			randomToPay = toPay;
		}
		listeners.firePropertyChange("randomToPay", 0, randomToPay);
		return randomToPay;
	}
	
	@Override
	public void wannaUpTemple(ITemple t) throws NotEnoughResourceException, StepMoveException, NotAvailableActionException {
		if (canUpTemples() == 0 && canUpDifferentTemples() == 0) {
			throw new NotAvailableActionException();
		}
		if (randomToPay > 0) {
			throw new NotAvailableActionException();
		}
		int pos = getTemplesManager().getPosition(t, getCurrentPlayer());
		// Si on est sur la derni�re marche
		if (pos >= t.getHeight() - 1) {
			throw new StepMoveException();
		}
		// Si on est sur l'avant-derni�re et qu'il y a quelqu'un sur la derni�re
		if (pos >= t.getHeight() - 2) {
			for (ITZPlayer p : getPlayers()) {
				if (p != getCurrentPlayer() && getTemplesManager().getPosition(t, p) >= t.getHeight() - 1) {
					throw new StepMoveException();
				}
			}
		}
		upTemple(getCurrentPlayer(), t);
		if (canUpTemples() > 0) {
			setUpTemples(canUpTemples() - 1);
		} else if (canUpDifferentTemples() > 0) {
			differentTemples.remove(t);
			setUpDifferentTemples(canUpDifferentTemples() - 1);
		}
	}
	
	@Override
	public void build(IFarm b) throws NotEnoughCornException, NotEnoughResourceException, NoAvailableSkullException, CannotBuildException {
		if (canBuildBuildings() < 1) {
			throw new CannotBuildException();
		}
		farm = b;
		prepareToPay(b.getCost());
	}
	
	@Override
	public void build(IActionBuilding b) throws NotEnoughCornException, NotEnoughResourceException, NoAvailableSkullException, CannotBuildException {
		if (canBuildBuildings() < 1) {
			throw new CannotBuildException();
		}
		building = b;
		prepareToPay(b.getCost());
	}
	
	@Override
	public void build(IMonument b) throws NotEnoughCornException, NotEnoughResourceException, NoAvailableSkullException, CannotBuildException {
		if (canBuildBuildings() < 1) {
			throw new CannotBuildException();
		}
		monument = b;
		prepareToPay(b.getCost());
	}
	
	@Override
	public void pay(TZResource r) {
		if (randomToPay < 1) {
			throw new IllegalStateException();
		}
		getCurrentPlayer().getWallet().removeResource(r);
		--randomToPay;
		listeners.firePropertyChange("randomToPay", randomToPay + 1, randomToPay);
		if (randomToPay == 0) {
			if (technology != null) {
				upTech(technology, getCurrentPlayer());
				technology = null;
			}
		}
	}
	
	@Override
	public void setToPay(int qty) {
		if (qty < 0) {
			throw new IllegalArgumentException();
		}
		randomToPay = qty;
		listeners.firePropertyChange("randomToPay", 0, randomToPay);
	}

	@Override
	public void setPayInCorn(boolean must) {
		mustPayInCorn = must;
	}
	
	@Override
	public void save(TZResource r) {
		if (canSave < 1 || toPay.getResources(r) < 1) {
			throw new IllegalStateException();
		}
		toPay.removeResource(r);
		--canSave;
		listeners.firePropertyChange("save", 0, canSave);
		if (canSave == 0) {
			finalPay();
		}
	}
	
	@Override
	public void saveSkull() {
		if (canSave < 1 || toPay.getSkulls() < 1) {
			throw new IllegalStateException();
		}
		try {
			toPay.removeSkull();
			--canSave;
			listeners.firePropertyChange("save", 0, canSave);
		} catch (NoAvailableSkullException e) { /* Rien - ne passe jamais par ici */ }
		if (canSave == 0) {
			finalPay();
		}
	}
	
	// OUTILS
	
	private void finalPay() {
		try {
			getCurrentPlayer().getWallet().remove(toPay);
		} catch (Exception e) { /* Rien - ne passe jamais ici */ }
		toPay.clear();
		Collection<TechLevel> techs = getTechnologies(getCurrentPlayer());
		if (building != null) {
			try {
				building.active(this, getCurrentPlayer());
			} catch (Exception e) { /* Rien */ }
			try {
				super.build(building);
				if (built == 0) {
					if (techs.contains(Architecture.PLUSCORN_ONBUILD)) {
						getCurrentPlayer().addCorn(1);
					}
					if (techs.contains(Architecture.PLUSSCORE_ONBUILD)) {
						getCurrentPlayer().addScore(2);
					}
				}
				building = null;
				setBuildBuildings(canBuildBuildings() - 1);
				built++;
			} catch (Exception e) {
				// TODO Bloc catch généré automatiquement
				e.printStackTrace();
			}
		}
		if (farm != null) {
			try {
				super.build(farm);
				if (built == 0) {
					if (techs.contains(Architecture.PLUSCORN_ONBUILD)) {
						getCurrentPlayer().addCorn(1);
					}
					if (techs.contains(Architecture.PLUSSCORE_ONBUILD)) {
						getCurrentPlayer().addScore(2);
					}
				}
			} catch (Exception e) { /* Rien */ }
			farm = null;
			if (built == 0) {
				if (techs.contains(Architecture.PLUSCORN_ONBUILD)) {
					getCurrentPlayer().addCorn(1);
				}
				if (techs.contains(Architecture.PLUSSCORE_ONBUILD)) {
					getCurrentPlayer().addScore(2);
				}
			}
			setBuildBuildings(canBuildBuildings() - 1);
			built++;
		}
		if (monument != null) {
			try {
				super.build(monument);
			} catch (Exception e) { /* Rien */ }
			monument = null;
			if (canBuildBuildings() > 0) {
				setBuildBuildings(canBuildBuildings() - 2);
			}
			setBuildMonuments(canBuildMonuments() - 1);
			built++;
		}
	}
	
	private void prepareToPay(ITZStock s) throws NotEnoughCornException, NotEnoughResourceException, NoAvailableSkullException {
		ITZPlayer p = getCurrentPlayer();
		// Vérifications
		if (mustPayInCorn) {
			p.removeCorn(getCornValue(s));
		} else {
			canSave = getTechnologies(p).contains(Technologies.Architecture.LESSRESOURCE_ONBUILD) ? 1 : 0;
			int save = canSave;
			for (TZResource r : TZResource.values()) {
				save += Math.min(0, p.getWallet().getResources(r) - s.getResources(r));
				if (save < 0) {
					throw new NotEnoughResourceException();
				}
			}
			if (p.getWallet().getSkulls() - s.getSkulls() < -save) {
				throw new NoAvailableSkullException();
			}
		
			// Pr�paration du paiement
			toPay.add(s);
			if (canSave == 0) {
				finalPay();
			} else {
				// Economie de canSave ressources
				for (TZResource r : TZResource.values()) {
					for (int i = 0; i < s.getResources(r) - canSave; ++i) {
						toPay.removeResource(r);
						p.getWallet().removeResource(r);
					}
				}
				for (int i = 0; i < s.getSkulls() - canSave; ++i) {
					toPay.removeSkull();
					p.getWallet().removeSkull();
				}
			}
		}
	}
	
	private int getCornValue(ITZStock a) {
		return a.getTotal() * CORN_RATIO;
	}
	
	protected ITechnologiesManager getTechnologyManager() {
		return technologyManager;
	}

}
